import uuid
import random
import smtplib
import json
from email.mime.text import MIMEText
from ldap3.utils.hashed import hashed
from ldap3 import Server, Connection, SUBTREE, ServerPool, HASHED_SALTED_SHA, ALL_ATTRIBUTES


class MyLdap(object):
    # LDAP类的初始化参数
    def __init__(self, ldap_host='',  # 你的LDAP地址
                 ldap_port='',  # LDAP端口
                 ldap_name='',  # LDAP域信息
                 ldap_passwd=''):  # LDAP认证密码
        self.ldap_host = ldap_host
        self.ldap_port = ldap_port
        self.ldap_name = ldap_name
        self.ldap_passwd = ldap_passwd
        self.ldap_obj = None
        self.ldap_connect(ldap_host, ldap_port, ldap_name, ldap_passwd)

    def ldap_connect(self, ldap_host, ldap_port, ldap_name, ldap_passwd):
        # 连接LDAP固定参数
        ldap_url = "{}:{}".format(ldap_host, ldap_port)
        server = Server(ldap_url, get_info='ALL')
        conn = Connection(server, ldap_name, ldap_passwd)
        conn.bind()
        self.ldap_obj = conn

    def ldap_search_gid(self, pm_name):
        '''
        LDAP搜索gid，根据部门名字
        :param pm_name:部门名字（e.g. 研发部）
        :return: gid
        '''
        search_parameters = {'search_base': 'dc=redcore,dc=cn',
                             'search_filter': '(&(!(ou=Redcore))(objectClass=posixGroup)(cn={}))'.format(pm_name)}
        self.ldap_obj.search(**search_parameters, attributes=ALL_ATTRIBUTES)
        resource = self.ldap_obj.response
        if len(resource) == 0:
            return {'未查询到该部门'}
        elif len(resource) > 1:
            return {'只支持单一部门id查询'}
        try:
            gid = resource[0].get('attributes').get('gidNumber')
        except Exception as e:
            return {'未查询到相关数据，数据异常。'}
        return gid

    def ldap_rendom_password(self):
        # 随机生成LDAP密码
        seed = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
        sa = []
        for i in range(16):
            sa.append(random.choice(seed))
        password = ''.join(sa)
        return password

    def ldap_add(self, firstName, lastName, partment_list, passwd, cname, mobile, title, mail=None):
        '''
        添加用户
        :param firstName: 名
        :param lastName: 姓
        :param partment_list:部门树 (e.g. ['研发部', '产品中心'])
        :param passwd:密码
        :param cname:中文名
        :param mobile:手机号
        :param title:岗位名称
        :param mail:邮箱
        :return:
        '''
        uid = firstName + '.' + lastName
        # 添加LDAP用户
        # 如果没有传入邮箱，则默认设置为名字+企业邮箱 例如：san.li@qqqq.com
        if mail is None:
            mail = '{}.qqqq.com'.format(uid)
        if passwd is None:
            passwd = self.ldap_rendom_password()
        hashed_password = hashed(HASHED_SALTED_SHA, passwd)
        hashed_password = hashed_password.replace('ssha', 'SSHA')  # 将sha 替换为 SHA
        # uid最好不要太长，对接一些应用时ldap账号无法使用，例如nas
        uidNumber = int(str(uuid.uuid1().int)[0:7])
        partment = partment_list[0]
        partment_tree = ''
        for p in partment_list:
            partment_tree = partment_tree + ',ou=' + p
        gid = self.ldap_search_gid(partment)
        # ldap dn 这块每个公司每个场景都不一致，需要自行判断
        dn = "uid={}{},ou=xxx,dc=xxx,dc=cn".format(uid, partment_tree)
        obj_class = ['inetOrgPerson', 'posixAccount', 'top']
        data = {'cn': cname,
                'uid': uid,
                'uidNumber': uidNumber,
                'gidNumber': gid,
                'title': title,
                'mobile': mobile,
                'givenName': firstName,
                'homeDirectory': '/home/users/{}'.format(uid),
                'loginShell': '/bin/sh',
                'sn': lastName,
                'mail': mail,
                'displayName': cname,
                'userPassword': hashed_password}
        ret = self.ldap_obj.add(dn, obj_class, data)
        print(dn, obj_class, data)
        if ret:
            print(uid, passwd)
            self.ldap_mail(mail, uid, passwd)
            return {"code": 0, "loginname": uid, "passwd": passwd, "mail": mail}
        return {"code": 1, "username": uid, "passwd": "账号已存在", "mail": mail}

    def ldap_search(self, user='*'):
        # 查询lDAP用户 默认输入 * 查询所有用户 ，需要自行判断
        search_parameters = {'search_base': 'dc=xxx,dc=cn',
                             'search_filter': '(&(!(ou=xxx))(objectClass=top)(uid={}))'.format(user)}
        self.ldap_obj.search(**search_parameters, attributes=ALL_ATTRIBUTES)
        search_list = self.ldap_obj.response
        user_list = []
        for i in search_list:
            tmp = i.get('attributes')
            ptree = tmp.get("objectClass")
            if 'organization' in ptree:
                continue
            elif 'organizationalUnit' in ptree:
                continue
            elif 'inetOrgPerson' in ptree:
                user_map = {}
                user_map["dn"] = i.get('dn') if i.get("dn") is not None else ''
                user_map["partment"] = i.get("dn").split(",ou=")
                user_map["user_cname"] = tmp.get("displayName") if tmp.get("displayName") is not None else ''
                user_map["uid"] = tmp.get("uid")[0] if tmp.get("uid") is not None else ''
                user_map["title"] = ''.join(list(tmp.get('title'))) if tmp.get("title") is not None else ''
                user_map["mail"] = tmp.get("mail")[0] if tmp.get("mail") is not None else ''
                user_map["mobile"] = tmp.get("mobile")[0] if tmp.get("mobile") is not None else ''
                user_map["userPassword"] = tmp.get("userPassword")[0] if tmp.get("userPassword") is not None else ''
                user_list.append(user_map)

        if not user_list:
            user_list = "用户不存在"
        return user_list

    def ldap_search_dn(self, username):
        '''
        只支持单一用户查询
        :param username:
        :return:
        '''

        search_parameters = {'search_base': 'dc=x'x'xxxx,dc=cn',
                             'search_filter': '(&(!(ou=xxx))(objectClass=top)(uid={}))'.format(username)}
        conn = self.ldap_obj.search(**search_parameters, attributes=ALL_ATTRIBUTES)
        search_list = self.ldap_obj.response
        if len(search_list) > 1:
            return {'只支持单一用户查询。'}
        if len(search_list) == 0:
            return {'未查询到该用户DN,请检查输入用户id。'}
        return search_list[0].get("dn")

    def ldap_del(self, username):
        if username == '*':
            return
        user_dn = self.ldap_search_dn(username)
        print(self.ldap_search(username))
        # 删除LDAP用户
        try:
            ret = self.ldap_obj.delete(user_dn)
            if ret:
                return {"code": 0, "msg": "success", "username": username}
            else:
                return {"code": 1, "msg": "failure", "username": username}
        except Exception:
            return {"code": 1, "msg": "failure", "username": username}
    
    def ldap_mail(self, receivers, username, password):
       """
       作用是添加用户后，给用户发个邮件，告诉他，吊毛，你可以使用ldap了
       """
        print(receivers, username, password)
        mail_host = "xxx"  # SMTP服务器
        mail_user = "xxxx"  # 发送邮箱的用户名
        mail_pass = r'xxxx'  # 密码(这里的密码不是登录邮箱密码，而是授权码)

        mail_sender = 'xxxxxxxx'  # 发件人邮箱
        mail_receivers = [receivers]  # 接收人邮箱

        content = '账号：{}  密码：{}\n 密码修改：xxxxx.html \n 登录sdp客户端后可以登录如下页面: \n NAS: xxxx  \n  Gitlab: xxxxx'.format(
            username, password)
        title = 'LDAP系统统一登录账号'  # 邮件主题
        message = MIMEText(content, 'plain', 'utf-8')  # 内容, 格式, 编码
        message['From'] = "{}".format(mail_sender)
        message['To'] = ",".join(mail_receivers)
        message['Subject'] = title

        try:
            smtpObj = smtplib.SMTP_SSL(mail_host, 465)  # 启用SSL发信, 端口一般是465
            smtpObj.login(mail_user, mail_pass)  # 登录验证
            smtpObj.sendmail(mail_sender, mail_receivers, message.as_string())  # 发送
            print("账号: {} 密码: {}".format(username, password))
        except smtplib.SMTPException:
            print("发送邮件失败")

    def ldap_auth(self, username, password):
        dn = self.ldap_search_dn(username)
        # check password by dn
        try:
            conn2 = Connection(self.ldap_host, user=dn, password=password, check_names=True, lazy=False,
                               raise_exceptions=False)
            conn2.bind()
            if conn2.result["description"] == "success":
                return True
            else:
                return False
        except Exception as e:
            return False

    def ldap_reload_json(self, filename):
        with open(filename, 'r', encoding="utf-8") as f:
            data = f.read()
        return json.loads(data)

    def ldap_get_values(self, data, tmp_list):
        for i in data.values():
            if isinstance(i, list):
                for j in i:
                    if isinstance(j, dict):
                        name = j.get("name")
                        email = j.get("email")
                        if name and email:
                            tmp_list.append((name, email))
                        else:
                            self.ldap_get_values(j, tmp_list)


if __name__ == '__main__':
    myldap = MyLdap()
    pass
